﻿using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;

namespace Monti.MVVM
{
   /// <summary>
   /// Logic for a TreeViewItemViewModel object that is used to display data in a treeview.
   /// </summary>
   public class TreeViewItemViewModel : INotifyPropertyChanged
   {
      #region Fields

      /// <summary>
      /// Dummy tree item to dynamically load sub items.
      /// </summary>
      static readonly TreeViewItemViewModel s_DummyChild = new TreeViewItemViewModel();

      /// <summary>
      /// Tree item text.
      /// </summary>
      private string m_Text;

      /// <summary>
      /// Is this item expanded or not.
      /// </summary>
      private bool m_IsExpanded;

      /// <summary>
      /// Is this item selected or not.
      /// </summary>
      private bool m_IsSelected;

      /// <summary>
      /// Is this item enabled or not.
      /// </summary>
      private bool m_IsEnabled;

      /// <summary>
      /// Will this node dynamically load it's children?
      /// </summary>
      private bool m_IsDynamicLoad;
      
      /// <summary>
      /// Icon to display.
      /// </summary>
      private ImageSource m_IconSource;

      /// <summary>
      /// Context menu for item.
      /// </summary>
      private ContextMenu m_ContextMenu;

      /// <summary>
      /// Additional data to attach.
      /// </summary>
      private object m_Tag;

      /// <summary>
      /// Parent item.
      /// </summary>
      private readonly TreeViewItemViewModel m_Parent;

      /// <summary>
      /// Children items.
      /// </summary>
      private readonly ObservableCollection<TreeViewItemViewModel> m_Children;

      #endregion

      #region Setup

      /// <summary>
      /// Initializes a new tree view item model.
      /// </summary>
      /// <param name="parent">Parent item.</param>
      public TreeViewItemViewModel (TreeViewItemViewModel parent) : this (parent, false)
      {
      }

      /// <summary>
      /// Initializes a new tree view item model.
      /// </summary>
      /// <param name="parent">Parent item.</param>
      /// <param name="dynamicLoad">True if this item will dynamically load it's children, otherwise false.</param>
      public TreeViewItemViewModel (TreeViewItemViewModel parent, bool dynamicLoad)
      {
         this.m_Text = string.Empty;
         this.IsEnabled = true;
         this.m_Parent = parent;
         this.m_IsDynamicLoad = dynamicLoad;
         this.m_Children = new ObservableCollection<TreeViewItemViewModel>();
         if (this.m_IsDynamicLoad) this.m_Children.Add(s_DummyChild);
      }

      /// <summary>
      /// Private constructor for dummy item.
      /// </summary>
      private TreeViewItemViewModel () { }

      #endregion

      #region Properties

      /// <summary>
      /// Gets or Sets if the node will dynamically load it's children.
      /// </summary>
      public bool DynamicLoad
      {
         get { return this.m_IsDynamicLoad; }
         set
         {
            if (this.m_IsDynamicLoad != value)
            {
               this.m_IsDynamicLoad = value;
               // If children are present, clear children and collapse.
               if (this.Children.Count > 0)
               {
                  this.Children.Clear ();
                  this.IsExpanded = false;
               }
               this.m_Children.Add (s_DummyChild);
               RaisePropertyChanged("DynamicLoad");
            }
         }
      }

      /// <summary>
      /// Gets or Sets the text to display.
      /// </summary>
      public string Text
      {
         get { return this.m_Text; }
         set
         {
            this.m_Text = value;
            this.RaisePropertyChanged("Text");
         }
      }

      /// <summary>
      /// Gets or Sets the context menu for the tree item.
      /// </summary>
      public ContextMenu ContextMenu
      {
         get { return this.m_ContextMenu; }
         set
         {
            this.m_ContextMenu = value;
            this.RaisePropertyChanged ("ContextMenu");
         }
      }

      /// <summary>
      /// Gets or Sets additional data attached to the node.
      /// </summary>
      public object Tag
      {
         get { return this.m_Tag; }
         set
         {
            this.m_Tag = value;
            this.RaisePropertyChanged("Data");
         }
      }

      /// <summary>
      /// Gets the collection of children.
      /// </summary>
      public ObservableCollection<TreeViewItemViewModel> Children
      {
         get { return this.m_Children; }
      }

      /// <summary>
      /// Gets the parent item.
      /// </summary>
      public TreeViewItemViewModel Parent
      {
         get { return this.m_Parent; }
      }

      /// <summary>
      /// Returns true if this object's Children have not yet been populated.
      /// </summary>
      public bool HasDummyChild
      {
         get { return this.Children.Count == 1 && this.Children[0] == s_DummyChild; }
      }

      /// <summary>
      /// Gets or Sets if this item is expanded or not.
      /// </summary>
      public bool IsExpanded
      {
         get { return this.m_IsExpanded; }
         set
         {
            if (value != this.m_IsExpanded)
            {
               this.m_IsExpanded = value;
               this.RaisePropertyChanged("IsExpanded");
            }

            if (this.m_IsExpanded && this.m_Parent != null)
               this.m_Parent.IsExpanded = true;

            if (this.HasDummyChild)
            {
               this.Children.Remove(s_DummyChild);
               this.LoadChildren();
            }
         }
      }

      /// <summary>
      /// Gets or Sets if this item is selected or not.
      /// </summary>
      public bool IsSelected
      {
         get { return this.m_IsSelected; }
         set
         {
            if (value != this.m_IsSelected)
            {
               this.m_IsSelected = value;
               this.RaisePropertyChanged("IsSelected");
            }
         }
      }

      /// <summary>
      /// Gets or Sets if this item is enabled.
      /// </summary>
      public bool IsEnabled
      {
         get { return this.m_IsEnabled; }
         set
         {
            if (value != this.m_IsEnabled)
            {
               this.m_IsEnabled = value;
               this.RaisePropertyChanged("IsEnabled");
            }
         }
      }

      /// <summary>
      /// Gets or Sets the path to the icon
      /// </summary>
      public ImageSource IconSource
      {
         get { return this.m_IconSource; }
         set
         {
            this.m_IconSource = value;
            this.RaisePropertyChanged ("IconSource");
         }
      }

      #endregion

      #region Methods

      /// <summary>
      /// Loads any children nodes.
      /// </summary>
      protected virtual void LoadChildren () { }

      /// <summary>
      /// Overriden to return text as string.
      /// </summary>
      /// <returns>Text for tree item.</returns>
      public override string ToString ()
      {
         return this.Text;
      }

      /// <summary>
      /// Overridden to make compiler happy.
      /// </summary>
      /// <returns>Base GetHasCode.</returns>
      public override int GetHashCode ()
      {
         return base.GetHashCode();
      }

      #endregion

      #region Property Changed

      /// <summary>
      /// Event used to notify when a property has changed.
      /// </summary>
      public event PropertyChangedEventHandler PropertyChanged;

      /// <summary>
      /// Fires the property changed event.
      /// </summary>
      /// <param name="propertyName">Name of property that changed.</param>
      protected void RaisePropertyChanged (string propertyName)
      {
         if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }

      #endregion
   }
}
